﻿using System;
using System.Data.Common;
using System.Globalization;
using System.Runtime.Serialization.Formatters;
using FluentNHibernate.Mapping;
using NHibernate;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Engine;
using NHibernate.Id;
using NHibernate.SqlTypes;
using NHibernate.Type;
using NHibernate.UserTypes;
using Newtonsoft.Json;

namespace OpenWaves.ActivityFeed.NHibernate
{
    public class UpdateMap : ClassMap<Update>
    {        
        public const int NVarcharMaxLength = SqlClientDriver.MaxSizeForLengthLimitedString + 1;
        private const string UpdateIdColumn = "UpdateId";
        private const string TopicsUpdateForeignKey = "FK_Topics_Update";
        private const string CommentsUpdateForeignKey = "FK_Comments_Update";
        private const string UserGroupsUpdateForeignKey = "FK_UserGroups_Update";
        private const string LikesUpdateForeignKey = "FK_Likes_Update";

        public UpdateMap()
        {
            Id(x => x.Id).GeneratedBy.Custom<StringTableHiLoGenerator>();

            Map(x => x.PublishedBy);
            Map(x => x.PublishedOn);
            Map(x => x.Source);
            Map(x => x.IsPublic);

            HasMany(x => x.Topics).Component(c => c.Map(t => t.Name).Column("Id")).AsSet().Cascade.All().KeyColumn(UpdateIdColumn).ForeignKeyConstraintName(TopicsUpdateForeignKey);

            HasMany(x => x.Comments).Component(c =>
                {
                    c.Map(comment => comment.PublishedBy);
                    c.Map(comment => comment.PublishedOn);
                    c.Map(comment => comment.Text).Length(NVarcharMaxLength);
                }).Access.CamelCaseField().Cascade.All().KeyColumn(UpdateIdColumn).ForeignKeyConstraintName(CommentsUpdateForeignKey);

            HasMany(x => x.UserGroups).Component(c => c.Map(u => u.Name).Column("Id")).KeyColumn(UpdateIdColumn).ForeignKeyConstraintName(UserGroupsUpdateForeignKey);

            HasMany(x => x.Likes).Component(c =>
                {
                    c.Map(l => l.UserId);
                    c.Map(l => l.CreatedDate);
                }).Cascade.All().KeyColumn(UpdateIdColumn).ForeignKeyConstraintName(LikesUpdateForeignKey);

            Map(x => x.Content).CustomType<UpdateContentType>();            

            Not.LazyLoad();
        }
    }

    public class StringTableHiLoGenerator : TableHiLoGenerator
    {
        public override object Generate(ISessionImplementor session, object obj)
        {
            return base.Generate(session, obj).ToString();
        }

        public override void Configure(IType type, System.Collections.Generic.IDictionary<string, string> parms, Dialect dialect)
        {
            base.Configure(NHibernateUtil.UInt32, parms, dialect);
        }
    }

    public class UpdateContentType : IUserType
    {
        private readonly JsonSerializerSettings _jsonSerializerSettings;

        public UpdateContentType()
        {
            this._jsonSerializerSettings = new JsonSerializerSettings()
                {
                    TypeNameHandling = TypeNameHandling.All,
                    TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
                };
        }

        public object Assemble(object cached, object owner)
        {
            var json = cached as string;
            return string.IsNullOrEmpty(json) ? null : JsonConvert.DeserializeObject(json, this._jsonSerializerSettings);
        }

        public object DeepCopy(object value)
        {
            return value;
        }

        public object Disassemble(object value)
        {
            return value ?? JsonConvert.SerializeObject(value, Formatting.Indented, this._jsonSerializerSettings);
        }

        bool IUserType.Equals(object x, object y)
        {
            return x == y;
        }

        public int GetHashCode(object x)
        {
            return x != null ? x.GetHashCode() : 0;
        }

        public bool IsMutable
        {
            get { return false; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            var val = rs[names[0]] as string;

            if (val != null && !string.IsNullOrEmpty(val) && val.Trim() != "")
            {
                return JsonConvert.DeserializeObject(val, this._jsonSerializerSettings);                                
            }

            return null;
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            var parameter = (DbParameter)cmd.Parameters[index];
            if (value == null)
            {
                parameter.Value = DBNull.Value;
            }
            else
            {
                parameter.Value = JsonConvert.SerializeObject(value, Formatting.Indented, this._jsonSerializerSettings);                 
            }
        }

        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        public Type ReturnedType
        {
            get { return typeof(UpdateContent); }
        }

        public SqlType[] SqlTypes
        {
            get { return new SqlType[] { new StringSqlType(UpdateMap.NVarcharMaxLength) }; }
        }        
    }
}